package com.panopset.flywheel;

import static com.panopset.Util.log;
import static com.panopset.Util.x;

import java.util.Stack;
import java.util.List;

import com.panopset.Strings;
import java.util.ArrayList;

/**
 * Let's get organized here. First load all the commands, from all templates,
 * don't add any structure.
 *
 * @author Karl Dinwiddie
 */
class RawCommandLoader {

    /**
     * Line count.
     */
    private int lineCount = 0;

    /**
     * Template.
     */
    private final Template t;

    /**
     * Line queue.
     */
    private final Stack<String> queue = new Stack<String>();

    /**
     * Commands to load.
     */
    private final List<Command> commands = new ArrayList<Command>();

    /**
     * Constructor.
     * @param template Template to load commands from.
     */
    RawCommandLoader(final Template template) {
        assert template != null;
        t = template;
    }

    /**
     * Load all commands.
     * @return List of all raw commands.
     */
    List<Command> load() {
        boolean firstTime = true;
        t.getStringLineSupplier().reset();
        String line = t.getStringLineSupplier().next();
        while (line != null) {
            if (t.getFlywheel().isStopped()) {
                return null;
            }
            flushQueue();
            lineCount++;
            if (firstTime) {
                firstTime = false;
            } else {
                loadCommand(new CommandText(t, Strings.getEol()));
            }
            if (t.getFlywheel().isStopped()) {
                log(x("Stopped at line") + ": " + lineCount);
                return null;
            }
            process(line);
            line = t.getStringLineSupplier().next();
        }
        flushQueue();
        return commands;
    }

    /**
     * Flush queue.
     */
    private void flushQueue() {
        while (!queue.isEmpty()) {
            process(queue.pop());
            if (t.getFlywheel().isStopped()) {
                return;
            }
        }
    }

    /**
     * Load command.
     * @param command Command to load.
     */
    private void loadCommand(final Command command) {
        if (t.getFirstCommand() == null) {
            t.setFirstCommand(command);
        }
        commands.add(command);
    }

    /**
     * Process line.
     *
     * @param line
     *            Line to process.
     */
    private void process(final String line) {
        int openDirectiveLoc = line.indexOf(Syntax.getOpenDirective());
        int closeDirectiveLoc = line.indexOf(Syntax.getCloseDirective());
        if (closeDirectiveLoc == -1 || openDirectiveLoc == -1) {
            loadCommand(new CommandText(t, line));
            return;
        }
        if (closeDirectiveLoc < openDirectiveLoc) {
            skipTo(line, openDirectiveLoc);
            return;
        }
        int endOfDirective = closeDirectiveLoc
                + Syntax.getCloseDirective().length();
        if (openDirectiveLoc == 0) {
            String remainder = line.substring(endOfDirective);
            if (remainder.length() > 0) {
                this.queue.push(remainder);
            }
            loadCommand(new Command.Builder().template(t).source(line,
                    closeDirectiveLoc).construct());
        } else {
            skipTo(line, openDirectiveLoc);
        }
    }

    /**
     * Skip to.
     *
     * @param ln
     *            Line to skip to.
     * @param pos
     *            Position to skip to.
     */
    private void skipTo(final String ln, final int pos) {
        loadCommand(new CommandText(t, ln.substring(0, pos)));
        this.queue.push(ln.substring(pos));
    }

    /**
     * @param commands
     *            Commands to add structure to.
     */
    public static void addStructure(final List<Command> commands) {
        if (commands == null) {
            return;
        }
        Command prev = null;
        for (Command command : commands) {
            command.setPrev(prev);
            if (prev != null) {
                prev.setNext(command);
            }
            prev = command;
        }
    }

}
